package robocup.component.situations;

import java.util.Arrays;
import java.util.LinkedList;

import robocup.component.ControllerNumbers;
import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.STAMINA_LEVEL;
import robocup.component.ControllerNumbers.TEAM;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.TurnAction;
import robocup.component.actions.Action.TYPE;
import robocup.component.geometry.Circle;
import robocup.component.geometry.Vektor;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.worldmodel.PlayersModel;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Line;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;

/**
 * This situation finds out which player will intercept the ball first and what
 * interception times will have the next players. It also calculates the
 * resulting intercept action for the fastest player of each team. If we are the
 * fastest player to intercept the ball it calculates where the ball should be
 * intercepted by using the {@link SituationEvaluator}. <br>
 * Ball interception is one of the most important things to do and will nearly
 * always be triggered when we are the fastest player to the ball. It is one of
 * the most important team skills.
 */
public class InterceptBallSituation
    extends Situation {

  // if a player does not intercept the ball after this number of cycles
  // he is not considered
  private int                maxCycles                   = DAInamiteConstants.PROPHET_MAX_CYCLES_INTERCEPTION;

  // if an opponent intercepts we will still look for teammates that intercept
  // in the following number of cycles
  private final int                maxTimeDiffToOpponent       = 5;

  // if a teammate intercepts we will still look for opponents that intercept
  // in the following number of cycles.
  private final int                maxTimeDiffToTeammate       = 5;

  // number of max. cycles when intercepting backwards (instead of turning and
  // in
  // tercepting forwards)
  private final int                maxBackwards                = DAInamiteConstants.PROPHET_MAX_BACKWARD;

  // the tolerance due to noise for opponents
  // -> kickable margin of opponents is reduced by this
  private final double                     securityOthers              = 0.0;

  // used to avoid heavy Vektor creation, take care on side effects!
  private final Vektor             dummyVektor                 = new Vektor();

  // used for ball position calculations
  private final Vektor             correctedBallPos            = new Vektor();

  /**
   * reference to the {@link PlayersModel}{@link #expectedPlayers}
   */
  private final Player[]           expectedPlayers;

  /**
   * the interception time for each player
   */
  private final int[]              interceptionTimes           = new int[22];

  /**
   * the interception times for this player. How much time do we need to get to
   * the point where the ball will be in <code>myInterceptionTimes</code>
   * cycles. Negative if we don't reach that point in time.
   */
  private final int[]              myInterceptionTimes         = new int[this.maxCycles];

  /**
   * The interception time to choose from <code>{@link #myInterceptionTimes}
   * </code>.
   */
  private int                myBestInterceptionTime      = Integer.MAX_VALUE;

  /**
   * the time left before an opponent will get the ball when when intercepting
   * after <code>{@link #myBestInterceptionTime}</code> cycles
   */
  private int                myCatchSecurity             = Integer.MAX_VALUE;

  /**
   * the interception mode when interception in a given number of cycles.
   * 
   * @see Movements#playerMovements
   */
  private final int[]              myInterceptionType          = new int[this.maxCycles];

  /**
   * number of cycles till the ball will be outside
   */
  private int                ballOutsideTime             = Integer.MAX_VALUE;

  /**
   * the ball will go into our goal.
   */
  private boolean            ballWillGoIntoOwnGoal       = false;

  /**
   * the ball will go into the opponent's goal.
   */
  private boolean            ballWillGoIntoOpponentsGoal = false;

  /**
   * who will get the ball first?
   */
  private TEAM               inBallPossession            = TEAM.UNCERTAIN;

  /**
   * who kicked last? (i.e. important after standard situations)
   */
  private Player             lastKickingPlayer           = null;

  /**
   * the drift for each player caused by its inertia.
   */
  private final Vektor[]           drift                       = new Vektor[22];

  /**
   * each players speed.
   */
  private final Vektor[]           playerSpeed                 = new Vektor[22];

  /**
   * the ball position for each cycle.
   */
  private final Vektor[]           ballPositions               = new Vektor[this.maxCycles];

  /**
   * the next action for each player.
   * 
   * we calculate opponents actions as well as we use them for our prediction
   * model. This is very important as if we would not predict the interception
   * actions of the fastest players the <code>{@link #inPossessionOfBall()}
   * </code>
   * flag might change very often leading to inconsistency.
   */
  private final Action[]           nextAction                  = new Action[22];

  /**
   * if flase don't care about this player as he will not be able to intercept.
   */
  private final boolean[]          considerPlayer              = new boolean[22];

  /**
   * the actions to be done by this player when intercepting in a given cycle.
   * null if no interception is possible for this cycle.
   */
  private final Action[]           myInterceptActions          = new Action[this.maxCycles];

  /**
   * this distance is needed by a player to control the ball. this distance
   * differs from player to player due to different kick margins. it may also
   * differ due to the factors <code>securityMates|securityOthers</code> or
   * for the goalies as they can catch the ball.
   */
  private final double[]           secureDistance              = new double[22];

  // init dummy player so we don't have to clone later
  private final Player  p = Player.getDummyInstance();
  private final Player  p2 = Player.getDummyInstance();

  private final Ball b = Ball.getDummyInstance();

  /**
   * reference to {@link Movements}{@link #playerMovements}
   */
  public final double[][][]        playerMovements;

  /**
   * the resulting ip.
   */
  private final Vektor             ip                          = new Vektor();

  /**
   * the resulting interception time.
   */
  private int                interceptionTime            = 50;

  /**
   * the players that are fastest to the ball. tolerance is defined by
   * <code>maxTimeDiffToOpponent|maxTimeDiffToTeammate</code>
   */
  private final LinkedList<Player> fastestPlayers              = new LinkedList<Player>();

  /**
   * all players that can kick the ball in the current cycle.
   */
  private final LinkedList<Player> playersThatCanKick          = new LinkedList<Player>();

  /**
   * true if this player can intercept in a previous cycle.
   */
  private boolean            meAlreadyIntercepted        = false;

  private final boolean            DEBUG                       = false;

  /**
   * true if a teammate can intercept in a previous cycle.
   */
  private boolean            friendAlreadyIntercepted;

  /**
   * this players intercept action was already calculated. don't calculate it
   * again.
   */
  private boolean            myNextActionWasCalculated;

  private double             offsideYOwn;

  private double             offsideYOther;

  /**
   * A situation which calculates, which player is fastest to the ball, assuming
   * they are willing to reach it as fast as possible
   * 
   * @param world -
   *          the current world-model
   * @param movements -
   *          a generic movement model
   */
  public InterceptBallSituation(
      final WorldModel world,
      final Prophet prophet,
      final Movements movements) {

    super(world, prophet, movements);
    
    // set references and fill arrays
    this.expectedPlayers = world.getExpectedPlayers();
    this.playerMovements = movements.getPlayerMovements();
    Arrays.fill(this.interceptionTimes, Integer.MAX_VALUE);
    Arrays.fill(this.nextAction, null);
    for (int i = 0; i < this.drift.length; i++) {
      this.drift[i] = new Vektor();
    }
    for (int i = 0; i < this.playerSpeed.length; i++) {
      this.playerSpeed[i] = new Vektor();
    }
    // create ball positions so we don't have to clone them later
    for (int i = 0; i < this.ballPositions.length; i++) {
      this.ballPositions[i] = new Vektor();
    }
  }

  /**
   * resets all local variables.
   * 
   */
  private void reset() {

    this.maxCycles = DAInamiteConstants.PROPHET_MAX_CYCLES_INTERCEPTION;
    this.interceptionTime = Integer.MAX_VALUE;
    this.myBestInterceptionTime = Integer.MAX_VALUE;

    this.fastestPlayers.clear();
    this.playersThatCanKick.clear();

    this.ballOutsideTime = Integer.MAX_VALUE;
    this.meAlreadyIntercepted = false;
    this.friendAlreadyIntercepted = false;
    this.benefit = 0.0;
    Arrays.fill(this.interceptionTimes, Integer.MAX_VALUE);
    Arrays.fill(this.myInterceptionTimes, Integer.MAX_VALUE);
    Arrays.fill(this.nextAction, null);
    Arrays.fill(this.myInterceptActions, null);
    Arrays.fill(this.myInterceptionType, -1);

    for (int i = 0; i < 22; i++) {
      this.playerSpeed[i].copy(this.getPlayer(i).getSpeed());
      this.drift[i].reset();
      this.secureDistance[i] = this.getSecureDist(this.getAllPlayers()[i]);
    }

    // unreliable players don't intercept (we hope)
    for (int i = 0; i < 22; i++) {
      if (this.expectedPlayers[i].isReliable() && !this.expectedPlayers[i]
          .wasOffsideWhenPassed()) {
        this.considerPlayer[i] = true;
      }
      else {
        this.considerPlayer[i] = false;
      }
    }

    
    
    
    switch (this.getWorld().getPlayMode()) {

      // opponents can't get to the ball
      case CORNER_KICK_OWN:
      case FREE_KICK_OWN:
      case GOAL_KICK_OWN:
      case INDIRECT_FREE_KICK_OWN:
      case PENALTY_READY_OWN:
      case KICK_IN_OWN:
    	  for (int i = 0; i < 11; i++) {
    		  if (i+1 >= 9)
    			  this.considerPlayer[i] = false;
    	  }
      case PENALTY_SETUP_OWN:
        for (int i = 11; i < this.considerPlayer.length; i++) {
          this.considerPlayer[i] = false;
        }
        break;

      // we can't get to the ball
      case CORNER_KICK_OTHER:
      case FREE_KICK_OTHER:
      case GOAL_KICK_OTHER:
      case INDIRECT_FREE_KICK_OTHER:
      case KICK_IN_OTHER:
      case PENALTY_SETUP_OTHER:
      case PENALTY_READY_OTHER:
        for (int i = 0; i < 11; i++) {
          this.considerPlayer[i] = false;
        }
        break;

      default:
        ;
    }

    this.inBallPossession = null;
    this.myNextActionWasCalculated = false;
  }

  /**
   * this method sets all values. it extrapolates the ball cycle by cycle and
   * finds out which players are able to intercept in this cycle. For that
   * purpose it determines how often each player has to turn before dashing to
   * the interception point. If a player is able to intercept in a given number
   * of cycles his corresponding action is saved for predicting his future
   * movement.
   */
  public void calculateSituation() {

    this.setUpdated();

    // no update when to far from ball as we don't see nothing anyway
    if (this.getWorld().getDistance(this.getWorld().getBallRef()) > 50 
    		&& this.fastestPlayers.size() > 0
    		&& (this.world.getBodyCycle() + this.getWorld().getNumber()) % 3 != 0) {
      return;
    }

    // force an update of the movements matrix whenever a players status changed
    // (i.e. when his type was communicated)
    final Player[] allPlayers = this.getWorld().getAllPlayers();
    for (final Player p : allPlayers) {
      if (p.isStatusChanged()) {
        this.getMovements().setPlayerMovements(p, true);
        p.setStatusChanged(false);
      }
    }

    // no interception in dead play modes
    if (this.getWorld().getPlayMode() == PLAY_MODE.BEFORE_KICK_OFF || this
        .getWorld().getPlayMode() == PLAY_MODE.KICK_OFF_OTHER) {
      return;
    }

    this.reset();

    // as our movement depends on the current stamina it has to be updated
    // if stamina changes
    this.getMovements().setPlayerMovements(this.getWorld().getSelfRef(), false);

    this.b.copy(this.getWorld().getBallRef());

    double distance;
    double secureDistance_;
    double angleToBall;
    double maxTurnAngle;
    boolean backward = false;
    final Vektor finalPosition = new Vektor();

    // extrapolate the ball for each cycle and find out
    // which player can intercept it at the resulting ball position
    for (int cycle = 0; cycle < this.maxCycles; cycle++) {

      this.ballPositions[cycle].copy(this.b.getPosition());

      // set outside cycle when extrapolated ball outside
      if (!this.getWorld().inField(this.b.getPosition()) && this.ballOutsideTime == Integer.MAX_VALUE) {
        this.ballOutsideTime = cycle;
      }

      // someone already intercepted and I am to far away
      // so i care only about the first interception
      if (this.interceptionTime != Integer.MAX_VALUE && this.interceptionTimes[this
          .getWorld().getNumber() - 1] == Integer.MAX_VALUE) {
        final double myDist = this.getWorld().getSelfRef().getDistance(this.b);
        if (myDist > Math
            .max(
                Math.max(5 * this.b.getSpeed().getLength(), 7) + this.playerMovements[this
                    .getWorld().getNumber() - 1][cycle][0], 5)) {
          break;
        }
      }

      // test for each considered player wether he can get the ball at the
      // resulting position in the given number of cycles
      for (int playerIndex = 0; playerIndex < 22; playerIndex++) {

        if (!this.considerPlayer[playerIndex]) {
          continue;
        }

        final Player p_ = this.getPlayer(playerIndex);

        // instead of adding a players drift to the player movement we substract
        // it from the ball which makes no difference
        this.correctedBallPos.reset();
        this.correctedBallPos.addToThis(this.b.getPosition());
        this.correctedBallPos.subFromThis(this.drift[playerIndex]);

        distance = p_.getDistance(this.correctedBallPos);
        // players secure radius
        secureDistance_ = this.secureDistance[playerIndex];

        // some players were not seen the last cycle(s) so we give them a
        // headstart
        int correctedCycle = cycle;

        // if the ball was kicked the last cycle by a teammate we assume that
        // the
        // each opponent needs one cycle to react. (he might by chance or
        // intelligent guessing have forseen our kick, but as we have never met
        // such an opponent ....)
        if (!p_.isMe()) {
          if (this.getWorld().getLastHeardMessageType() == MESSAGES.KICKED_BALL) {
            if (!p_.isFriend() && correctedCycle > 0) {
              correctedCycle--;
            }
          }
        }

        // if a player is tackle dead he can not intercept
        correctedCycle -= this.getPlayer(playerIndex).getTackledeadCountdown();
        if (correctedCycle < 0) {
          continue;
        }

        correctedCycle = Math.min(this.maxCycles - 1, correctedCycle);

        // try the best case and if even in this case the player can't intercept
        // continue
        if (distance - secureDistance_ > this.playerMovements[playerIndex][correctedCycle][0]) {
          continue;
        }

        // try all interception modes...

        // dashing only
        angleToBall = p_.getAngleRel(this.correctedBallPos,
            Player.RELATIVE.TO_BODY);
        backward = Math.abs(angleToBall) > 90 && cycle <= this.maxBackwards;

        // ball can be reached by only dashing
        if (Math.abs(Math.sin(Math.toRadians(angleToBall))) * distance < secureDistance_) {
          if (!backward) {
            // the resulting player position after the given number of cycles
            finalPosition.pointAtPolar(
                this.playerMovements[playerIndex][correctedCycle][0], p_
                    .getBodyDir());
            finalPosition.addToThis(p_.getPosition());

            // test if the resulting player will be near enough to the resulting
            // ball
            if (this.playerMovements[playerIndex][correctedCycle][0] > Math
                .abs(Math.cos(Math.toRadians(angleToBall))) * distance || this.correctedBallPos
                .getDistance(finalPosition) < secureDistance_) {

              // save the result
              this.finalize(p_, cycle, BasicActionFactory.dashToPoint(p_, this.b
                  .getPosition(), correctedCycle));
              if (p_.isMe()) {
                this.rememberMyIP(p_, cycle, BasicActionFactory.dashToPoint(p_,
                    this.b.getPosition(), cycle), distance, secureDistance_, 0);
              }
              continue;
            }
          }
          // backward case
          else if (backward) {
            finalPosition.pointAtPolar(
                this.playerMovements[playerIndex][correctedCycle][4], p_
                    .getBodyDir() + 180);
            finalPosition.addToThis(p_.getPosition());

            // test if the resulting player will be near enough to the resulting
            // ball
            if (this.playerMovements[playerIndex][correctedCycle][4] > Math
                .abs(Math.cos(Math.toRadians(angleToBall))) * distance || this.correctedBallPos
                .getDistance(finalPosition) < secureDistance_) {

              // save the results
              this.finalize(p_, cycle, BasicActionFactory.dashToPoint(p_, this.b
                  .getPosition(), correctedCycle));
              if (p_.isMe()) {
                this.rememberMyIP(p_, cycle, BasicActionFactory.dashToPoint(p_,
                    this.b.getPosition(), cycle), distance, secureDistance_, 4);
              }
              continue;
            }
          }
        }

        maxTurnAngle = 180.0 / (1 + p_.getSpeed().getLength() * p_.getPConf().INERTIA_MOMENT);

        // turning 1x ->foreward
        if (Math.abs(angleToBall) < maxTurnAngle && distance < this.playerMovements[playerIndex][correctedCycle][1] + secureDistance_) {

          // save the results
          this.finalize(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
              this.correctedBallPos));
          if (p_.isMe()) {
            this.rememberMyIP(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
                this.correctedBallPos), distance, secureDistance_, 1);
          }
          continue;
        }

        // turning 1x ->backward
        if (cycle <= this.maxBackwards && 180 - Math.abs(angleToBall) < maxTurnAngle && distance < this.playerMovements[playerIndex][correctedCycle][5] + secureDistance_) {

          // save the results
          this.finalize(p_, cycle, BasicActionFactory.turnBackToPoint(p_,
              this.correctedBallPos));
          if (p_.isMe()) {
            this.rememberMyIP(p_, cycle, BasicActionFactory.turnBackToPoint(p_,
                this.correctedBallPos), distance, secureDistance_, 5);
          }
          continue;
        }

        maxTurnAngle += 180.0 / (1 + this.playerSpeed[playerIndex].getLength() * p_
            .getPConf().PLAYER_DECAY * p_.getPConf().INERTIA_MOMENT);

        // turning 2x
        if (distance < this.playerMovements[playerIndex][correctedCycle][2] + secureDistance_) {
          if (Math.abs(angleToBall) < maxTurnAngle) {

            // save the results
            this.finalize(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
                this.correctedBallPos));
            if (p_.isMe()) {
              this.rememberMyIP(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
                  this.correctedBallPos), distance, secureDistance_, 2);
            }
            continue;
          }
        }

        // turning 3x
        if (distance < this.playerMovements[playerIndex][correctedCycle][3] + secureDistance_) {

          // save the results
          this.finalize(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
              this.correctedBallPos));
          if (p_.isMe()) {
            this.rememberMyIP(p_, cycle, BasicActionFactory.turnBodyToPoint(p_,
                this.correctedBallPos), distance, secureDistance_, 3);
          }
          continue;
        }
      }

      if (cycle < 5) {
        this.extrapolateDrift();
      }

      // extrapolate the ball
      if (this.ballOutsideTime == Integer.MAX_VALUE) {
        this.b.extrapolate(1);
      }
    }

    // if this plyer is the fastest to the ball try to find the best
    // interception
    // mode/point
    if (this.iAmFastestToBall()) {

      this.myCatchSecurity = this.getMyCatchSecurity();
      this.myBestInterceptionTime = this
          .getBestInterceptionTime(this.myCatchSecurity);
    }

    // remember who was in possession of the ball
    if (this.getWorld().isViBased()) {
      this.getWorld().getSelfRef().getBallPossessions().insert(
          this.inPossessionOfBall());
      if (this.interceptionTime == 0) {
        this.lastKickingPlayer = this.fastestPlayers.get(0);
      }
    }
    this.setCorrectedOffside();

  }

  /**
   * the offside limit is not defined by the ball anymore but by the next ip
   */
  private void setCorrectedOffside() {

    if (this.interceptionTime == Integer.MAX_VALUE || this.getWorld()
        .getPlayMode() == PLAY_MODE.KICK_IN_OWN || this.getWorld()
        .getPlayMode() == PLAY_MODE.KICK_IN_OTHER) {
      this.offsideYOwn = SConf.getInstance().half_length;
      this.offsideYOther = -SConf.getInstance().half_length;
    }
    else {
      this.offsideYOwn = Math.max(this.getWorld().getOffsideY(true), this.ip.y);
      this.offsideYOther = Math.min(this.getWorld().getOffsideY(false),
          this.ip.y);
    }
  }

  /**
   * extrapolates player positions when they don't move.
   * 
   */
  private void extrapolateDrift() {

    for (int i = 0; i < this.drift.length; i++) {
      this.drift[i].addToThis(this.playerSpeed[i]);
      this.playerSpeed[i].multThis(this.getPlayer(i).getPConf().PLAYER_DECAY);
    }
  }

  /**
   * playerIndex -> player
   * 
   * @param index -
   *          the player index [0..10] teammates, [11..21] opponents
   * @return the player corresponding to the index
   */
  private Player getPlayer(final int index) {

    return this.expectedPlayers[index];
  }

  /**
   * player -> playerIndex
   * 
   * @param p -
   *          the player
   * @return the players index
   * 
   * @see #getPlayer(int)
   */
  private int getPlayerIndex(final Player p) {

    if (p.isFriend()) {
      return p.getNumber() - 1;
    }
    else {
      return p.getNumber() - 1 + 11;
    }
  }

  /**
   * this method saves the values after determing a players interception time.
   * 
   * @param p -
   *          the player
   * @param interceptTime -
   *          the interception time
   * @param a -
   *          the first action when intercepting
   */
  private void finalize(final Player p,
      final int interceptTime,
      final Action a) {

    if (p.isMe() && this.meAlreadyIntercepted) {
      return;
    }

    // the player is the fastest -> set interception point and time
    if (this.fastestPlayers.size() == 0) {
      this.fastestPlayers.add(p);
      this.interceptionTime = interceptTime;
      this.interceptionTimes[this.getPlayerIndex(p)] = interceptTime;
      this.ip.copy(this.ballPositions[interceptTime]);
      this.setAction(p, a);
    }
    // the player is not the fastest
    // -> remember the player and store the action
    else {
      this.fastestPlayers.add(p);
      this.interceptionTimes[this.getPlayerIndex(p)] = interceptTime;
      this.setAction(p, a);
    }
    // we are only interested in the fastest interception for all players but
    // ourself
    if (!p.isMe()) {
      this.considerPlayer[this.getPlayerIndex(p)] = false;
    }

    // if interception occured reduce the max cycles
    if (!p.isMe() && p.isFriend() && !this.fastestPlayers.get(0).isMe()) {
      this.maxCycles = Math.min(this.maxCycles,
          (int) (interceptTime * 1.2) + this.maxTimeDiffToTeammate);
    }

    if (!p.isFriend()) {
      if (this.friendAlreadyIntercepted && !this.meAlreadyIntercepted) {
        this.maxCycles = Math.min(this.maxCycles,
            (int) (interceptTime * 1.2) + 1);
      }
      else {
        this.maxCycles = Math.min(this.maxCycles,
            (int) (this.interceptionTime * 1.2) + this.maxTimeDiffToOpponent);
      }
    }

    if (p.isMe()) {
      this.meAlreadyIntercepted = true;

      // limit horizont
      // (you should turn this off or at least modifiy it when using
      // late interception)
      this.maxCycles = Math.min(this.maxCycles,
          (int) (interceptTime * 2) + this.maxTimeDiffToOpponent);
    }

    if (p.isFriend()) {
      this.friendAlreadyIntercepted = true;
    }
  }

  /**
   * saves the interception points for this player.
   * 
   * @param p -
   *          the self
   * @param interceptTime -
   *          the interception time
   * @param a -
   *          the interception action
   * @param distance -
   *          the overlap when intercepting (the movement a player could do
   *          minus the movement the player has to do for getting the ball.)
   *          <code>distance</code> is always > 0 as otherwise there would be
   *          no interception.
   * @param secureDistance -
   *          the players to the ball which is taken as secure (PLAYER_SIZE +
   *          BALL_SIZE + KICKABLE_MARGIN - SECURITY)
   * @param interceptType -
   *          the interception type as described in {@link Movements}
   */
  private void rememberMyIP(final Player p,
      final int interceptTime,
      final Action a,
      final double distance,
      final double secureDistance,
      final int interceptType) {

    int cyclesToPoint = interceptTime;

    // find out how much time is needed to get to the interception point
    while (cyclesToPoint >= 0 
        && this.playerMovements[this.getPlayerIndex(p)][cyclesToPoint][interceptType] > distance - secureDistance) {
      cyclesToPoint--;
    }
    this.myInterceptionTimes[interceptTime] = cyclesToPoint + 1;
    this.myInterceptActions[interceptTime] = a;
    this.myInterceptionType[interceptTime] = interceptType;

    // if we have time, we don't hurry but save some stamina instead
    if (a != null 
        && a.getType() == TYPE.DASH 
        && (this.myInterceptionTimes[interceptTime] > cyclesToPoint + 2 
            || (this.myInterceptionTimes[interceptTime] < 3 
                && this.myInterceptionTimes[interceptTime] > cyclesToPoint + 1))) {
      this.dummyVektor.copy(this.getWorld().getSelfRef().getPosition());
      this.dummyVektor.addToThis(this.getWorld().getSelfRef().getSpeed());

      if (this.dummyVektor.getDistance(this.ballPositions[interceptTime]) > 0.8) {
        p.correctDashPower((DashAction) a, STAMINA_LEVEL.MEDIUM);
      }
      else {
        p.correctDashPower((DashAction) a, STAMINA_LEVEL.MAX);
      }
    }
  }

  /**
   * the secure distance based on player. (PLAYER_SIZE + BALL_SIZE +
   * KICKABLE_MARGIN - SECURITY)
   * 
   * @param p -
   *          the player
   * @return the distance needed to control the ball
   */
  private double getSecureDist(final Player p) {

    if (p.isGoalie()) {
      if (p.isFriend()) {
        if (this.getWorld().inOwnPenaltyArea(this.b.getPosition().x,
            this.b.getPosition().y, SConf.getInstance().BALL_SIZE)) {
          return p.getCatchDistance();// - this.securityMates;
        }
        else {
          return p.getKickDistance();// - this.securityMates;
        }
      }
      else {
        return p.getCatchDistance() - this.securityOthers;
      }
    }
    else {
      if (p.isFriend()) {
        return p.getKickDistance();// - this.securityMates;
      }
      else {
        return p.getKickDistance() - this.securityOthers;
      }
    }
  }

  /**
   * @return true. if self is among the fastest players
   */
  public boolean iAmFastestToBall() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    for (final Player p : this.fastestPlayers) {
      if (p.isMe() && this.interceptionTimes[this.getPlayerIndex(p)] == this.interceptionTime) {
        return true;
      }
      if (this.interceptionTimes[this.getPlayerIndex(p)] != this.interceptionTime) {
        return false;
      }
    }
    return false;
  }

  /**
   * sets the calculated action to the array.
   * 
   * @param p -
   *          the player
   * @param a -
   *          the best interception action for the player, null if not
   *          intercepting
   */
  public void setAction(final Player p,
      final Action a) {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    if (p.isFriend()) {
      this.nextAction[p.getNumber() - 1] = a;
    }
    else {
      this.nextAction[p.getNumber() - 1 + 11] = a;
    }
  }

  /**
   * It is not always preferable to intercept the ball fastest possible.
   * Sometimes it is more secure to go to a later interception point. Sometimes,
   * the ball already flies in the right direction so we just follow and
   * intercept later. This method calculates the best interception time. Based
   * on the balls speed in attack direction.
   * 
   * @param catchSecurity -
   *          the time this player has till some opponentn intercepts
   * @return - the best time to intercept the ball
   */
  private int getBestInterceptionTime(final int catchSecurity) {

    // not safe -> intercept as fast as possible
    if (catchSecurity <= this.maxTimeDiffToOpponent) {
      return this.interceptionTime;
    }

    final Ball b = this.getWorld().getBallRef().cloned();
    b.extrapolate(this.interceptionTime + 1);

    int bestInterceptionTime = this.interceptionTime;

    // if the ball is fast, it is easier to miss it so we take the first
    // point
    if (b.getSpeed().getLength() > 1.5 || b.getSpeed().getLength() < 0.4) {
      return bestInterceptionTime;
    }

    // this case is handled by the DribbleSituation
    if (this.interceptionTime == 0 && this.myInterceptActions[1] != null) {
      this.benefit = 0.0;
      return this.interceptionTime;
    }

    this.benefit = 0.0;
    double tmpValue = 0.0;
    double ballSpeedInGoalDir;
    int interceptionDelay = 0;

    // for each interception possibility calculate the benefit of the resulting
    // situation
    for (int cycle = this.interceptionTime + 1; cycle < this.maxCycles 
          && cycle < this.ballOutsideTime 
          && cycle < this.interceptionTime + catchSecurity; cycle++) {

      // no action, no fun
      if (this.myInterceptActions[cycle] == null) {
        continue;
      }

      interceptionDelay = cycle - this.myInterceptionTimes[cycle];

      // we have much time, so we don't intercept backwards but turn instead
      if (interceptionDelay > 3 && this.myInterceptionType[cycle] == 4) {
        this.myInterceptActions[cycle] = BasicActionFactory.turnBodyToPoint(
            this.getWorld().getSelfRef(), this.ballPositions[cycle]);
        this.myInterceptionType[cycle] = 2;
        this.myInterceptionTimes[cycle] = cycle - interceptionDelay + 2;
        // System.out.println(this.getWorld().getCycleAndPlayerString()
        // + " Turning instead of backward " +
        // this.myInterceptActions[cycle] + " " + interceptionDelay);
      }
      // we have much time, so we don't intercept backwards but turn instead
      else if (interceptionDelay > 2 && this.myInterceptionType[cycle] == 5) {
        this.myInterceptActions[cycle] = BasicActionFactory.turnBodyToPoint(
            this.getWorld().getSelfRef(), this.ballPositions[cycle]);
        this.myInterceptionType[cycle] = 2;
        this.myInterceptionTimes[cycle] = cycle - interceptionDelay + 1;
      }

      // get the benefit for the resulting situation
      tmpValue = this.getSituationEvaluator().getValue(
          SituationEvaluator.TYPE.BALL_INTERCEPTION, this.getWorld()
              .getBallRef().getPosition(), b.getPosition(), Math.min(5,
              this.interceptionTime + (int) (catchSecurity * (b.getSpeed()
                  .getLength() + 0.5)) - cycle), cycle, this.getWorld()
              .getSelfRef());

      if (true || cycle > this.interceptionTime) {

        // punish backward
        if (this.myInterceptionType[cycle] > 3) {
          tmpValue *= 0.8 * Math.max(0.1,
              1 - this.myInterceptionTimes[cycle] / 30.0);
        }
      }

      // if a better soluation was found -> make some more considerations
      if (tmpValue > this.benefit) {
        if (cycle > this.interceptionTime) {
          this.dummyVektor.copy(SConf.getInstance().GOAL_POS_OTHER);
          this.dummyVektor.subFromThis(b.getPosition());
          this.dummyVektor.setLength(1.0);
          ballSpeedInGoalDir = this.dummyVektor.scalar(b.getSpeed());

          // no sense taking a late intercept as the ball goes in the wrong
          // direction -> punish it
          if (ballSpeedInGoalDir <= 0.3) {
            tmpValue *= 0.5;
          }
          // no sense taking a late intercept even so the ball goes in the right
          // direction as the player is faster than the ball
          else if (ballSpeedInGoalDir - this.getWorld().getSelfRef().getSpeed()
              .getLength() < 0.6) {
            tmpValue *= 0.75;
          }
        }
      }

      // if a better soluation was found -> store it
      if (tmpValue > this.benefit) {
        bestInterceptionTime = cycle;
        this.benefit = tmpValue;
      }
      else {
        break;
      }

      b.extrapolate(this.interceptionTime);

    }

    return bestInterceptionTime;
  }

  /**
   * gets the calculated action for a given player from the array.
   * 
   * @param p -
   *          the player
   * @return last calculated intercept ball action, null if player isn't among
   *         the fastest players
   */
  public Action getNextAction(final Player p) {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    if (p.isMe() && this.iAmFastestToBall() && !this.ballOutBeforeIntercept() && !this.myNextActionWasCalculated) {

      this.myNextActionWasCalculated = true;

      // if near interception, do not only intercept but intercept the best
      // way possible
      if (this.myBestInterceptionTime < 3 || this.getWorld().getSelfRef()
          .getDistance(this.ballPositions[this.myBestInterceptionTime]) < 0.9) {
        this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
        this.nextAction[this.getWorld().getNumber() - 1] = this
            .getNextCloseInterceptAction(this.myBestInterceptionTime);
        return this.nextAction[this.getWorld().getNumber() - 1];
      }
      // same as above but kept simpler
      else if (this.myBestInterceptionTime < 7) {
        final Action correctedInterceptAction = this
            .optimizeInterception(this.myBestInterceptionTime);

        if (correctedInterceptAction != null) {
          this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
          this.nextAction[this.getWorld().getNumber() - 1] = correctedInterceptAction;
          return this.nextAction[this.getWorld().getNumber() - 1];
        }
      }

      // standard interception for far away balls
      Action myAction = this.myInterceptActions[this.myBestInterceptionTime];
      if (myAction == null) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + " InterceptBallSituation - action = null");
        return null;
      }

      // dashing is always prefered as it keeps us going
      if (myAction.getType() == Action.TYPE.DASH) {
        final DashAction dash = (DashAction) myAction;
        if (dash.getPower() > 0 || this.myBestInterceptionTime - this.interceptionTime < 3) {
          this.ip.copy(this.ballPositions[this.myBestInterceptionTime]);
          this.nextAction[this.getWorld().getNumber() - 1] = myAction;
          return myAction;
        }
      }

      // if we would have to turn instead we try to ind if one of the later
      // points might be intercepted in a save way by dashing
      // without this the player would turn too often

      // try if dashing gets the ball the previous cycle
      for (int cycle = this.myBestInterceptionTime; cycle > this.myBestInterceptionTime - 2 && cycle > 0; cycle--) {
        myAction = this.myInterceptActions[cycle];
        if (myAction == null) {
          continue;
        }
        if (myAction.getType() == Action.TYPE.DASH) {
          this.myBestInterceptionTime = cycle;
          this.ip.copy(this.ballPositions[cycle]);
          this.nextAction[this.getWorld().getNumber() - 1] = myAction;
          return myAction;
        }
      }

      // try if dashing gets the ball in one of the next cycles
      for (int cycle = this.myBestInterceptionTime; cycle < this.myBestInterceptionTime + this.myCatchSecurity - 2 && cycle < this.maxCycles; cycle++) {
        myAction = this.myInterceptActions[cycle];
        if (myAction == null) {
          continue;
        }
        if (myAction.getType() == Action.TYPE.DASH) {
          this.myBestInterceptionTime = cycle;
          this.ip.copy(this.ballPositions[cycle]);
          this.nextAction[this.getWorld().getNumber() - 1] = myAction;
          return myAction;
        }
      }

      // try an earlier point if more save
      for (int cycle = this.myBestInterceptionTime; cycle >= this.interceptionTime; cycle--) {
        if (this.myInterceptionTimes[cycle] < cycle) {
          this.myBestInterceptionTime = cycle;
          this.ip.copy(this.ballPositions[cycle]);
          this.nextAction[this.getWorld().getNumber() - 1] = this.myInterceptActions[cycle];
          return this.myInterceptActions[cycle];
        }
      }

      // try a later point if more save
      for (int cycle = this.myBestInterceptionTime; cycle < this.myBestInterceptionTime + this.myCatchSecurity - 2 && cycle < this.maxCycles; cycle++) {
        if (this.myInterceptionTimes[cycle] < cycle) {
          this.myBestInterceptionTime = cycle;
          this.ip.copy(this.ballPositions[cycle]);
          this.nextAction[this.getWorld().getNumber() - 1] = this.myInterceptActions[cycle];
          return this.myInterceptActions[cycle];
        }
      }

      return this.nextAction[p.getNumber() - 1];
    }

    // return the stored values
    if (p.isFriend()) {
      return this.nextAction[p.getNumber() - 1];
    }
    else {
      return this.nextAction[p.getNumber() - 1 + 11];
    }
  }

  /**
   * close interception is calculated in more detail.
   * 
   * @return intercept Action for near balls
   */
  private Action getNextCloseInterceptAction(final int delay) {

    // set next ball
    this.b.copy(this.getWorld().getBallRef());
    this.b.extrapolate(delay);

    // next player when dashing to ball
    this.p.copy(this.getWorld().getSelfRef());
    this.p
        .predictThisAfterAction(this.myInterceptActions[this.interceptionTime]);

    final double distanceWhenDashing = this.b.getDistance(this.p);

    final boolean collisionPoss = distanceWhenDashing < this.p.getPConf().PLAYER_SIZE && !this
        .getWorld().getSelfRef().isGoalie();
    boolean ballFast = this.b.getSpeed().getLength() > 1.8;
    final boolean nearToNoDash = this.myInterceptActions[this.interceptionTime] != null && this.myInterceptActions[this.interceptionTime] instanceof DashAction && Math
        .abs(((DashAction) this.myInterceptActions[this.interceptionTime])
            .getPower()) < 5;

    // player without dashing
    this.p2.copy(this.getWorld().getSelfRef());
    this.p2.extrapolate(1);

    final double distanceWithoutDashing = this.b.getDistance(this.p2);

    this.dummyVektor.copy(this.getPointToTurnTo());

    // needed if player isn't turned in direction of opp. goal
    // turning is very useful as we might pass to someone we don't see
    // without it
    boolean turningNeeded = Math.abs(this.p2.getAngleRel(this.dummyVektor,
        Player.RELATIVE.TO_BODY)) > 20 && !this.getWorld().getSelfRef()
        .isGoalie();

    // ball fast but no dashing needed for collision so we turn
    if (distanceWithoutDashing < this.p.getPConf().PLAYER_SIZE * 0.8 && turningNeeded && ballFast) {
      if (this.DEBUG) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + " CloseIntercept1 - turning for collision");
      }
      return BasicActionFactory.turnBodyToPoint(this.getWorld().getSelfRef(),
          this.dummyVektor);
    }

    // dashing needed for collision or no turn needed -> dash to stop ball
    if (collisionPoss && (ballFast || !turningNeeded)) {
      if (this.DEBUG) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + " CloseIntercept1 - dashing for collision");
      }
      return this.nextAction[this.getWorld().getNumber() - 1];
    }

    // try turning to a better direction as the ball comes to us
    if ((distanceWithoutDashing < this.p2.getKickDistance() * 0.9 || nearToNoDash) && turningNeeded) {
      this.p2.copy(this.getWorld().getSelfRef());
      this.p2.predictThisAfterAction(BasicActionFactory.turnBodyToPoint(this
          .getWorld().getSelfRef(), this.dummyVektor));
      // we have to turn at least 2x -> try to collide and slow down (and
      // turn 1x afterwards)
      if (Math.abs(this.p2.getAngleRel(this.dummyVektor,
          Player.RELATIVE.TO_BODY)) > 20.0 && collisionPoss) {
        return this.myInterceptActions[this.interceptionTime];
      }
      return BasicActionFactory.turnBodyToPoint(this.getWorld().getSelfRef(),
          this.dummyVektor);
    }

    // we have to dash and the ball is slow enough to be kicked in next
    // cycle -> get the ball to a good kick position by trying to get the ball
    // to a certain distance to the next player
    if (((distanceWhenDashing < this.p2.getPConf().KICKABLE_MARGIN - 0.2) || (this
        .getWorld().getSelfRef().isGoalie() && distanceWhenDashing < 1.8) && !ballFast)) {
      final double bestDist = SConf.getInstance().BALL_SIZE + this.p.getPConf().PLAYER_SIZE + this.p
          .getPConf().KICKABLE_MARGIN / 6;

      final Line playerAccelLine = new Line(this.p2.getPosition(), this.p2
          .getPosition().add(new Vektor(1.0, this.p2.getBodyDir())));
      final Circle bestCircle = new Circle(this.b.getPosition(), bestDist);

      final LinkedList<Vektor> bestIPoints = playerAccelLine
          .getIntersectionPoints(bestCircle);
      Vektor bestIPoint = null;

      // Ipoint exists
      if (bestIPoints.size() > 0) {
        // tangent
        if (bestIPoints.size() == 1) {
          bestIPoint = bestIPoints.get(0);
        }
        // secant
        else {
          final double angle0 = this.p2.getAngleAbs(bestIPoints.get(0),
              Player.RELATIVE.TO_BODY);
          final double angle1 = this.p2.getAngleAbs(bestIPoints.get(1),
              Player.RELATIVE.TO_BODY);
          if (Math.abs(angle0) < Math.abs(angle1)) {
            bestIPoint = bestIPoints.get(0);
          }
          else {
            bestIPoint = bestIPoints.get(1);
          }
        }

        return BasicActionFactory.dashToPoint(this.getWorld().getSelfRef(),
            bestIPoint);
      }
    }

    return this.myInterceptActions[this.interceptionTime];
  }

  /**
   * optimizes a given interception. first we only checked if dashing could get
   * the ball. Now we check if instead of only dashing a turn as first action
   * would bring us closer. This is use for near but not close intercepts.
   * 
   * @param interceptionTime -
   *          our interception time
   * @return - the optimized intercept action, null if no optimization was found
   */
  private Action optimizeInterception(final int interceptionTime) {

    // only near intercepts, only dashing as turning will already get as close
    // as possible to the ball, only save
    if (interceptionTime > 6 || this.myInterceptActions[interceptionTime] instanceof TurnAction || this.myInterceptionTimes[interceptionTime] == interceptionTime) {
      return null;
    }

    this.p.copy(this.getWorld().getSelfRef());
    this.p.extrapolate(interceptionTime);

    final double angleToBall = this.p.getAngleRel(
        this.ballPositions[interceptionTime], RELATIVE.TO_BODY);
    final double distanceToBall = this.p
        .getDistance(this.ballPositions[interceptionTime]);

    final double distanceWhenDashing = Math
        .abs(Math.sin(Math.toRadians(angleToBall))) * distanceToBall;

    final double interceptDistance = (this.p.isGoalie() && this.getWorld()
        .inOwnPenaltyArea(this.ballPositions[interceptionTime].x,
            this.ballPositions[interceptionTime].y, 0)) ? this.p
        .getCatchDistance() : this.p.getKickDistance();

    // dashing best, as it is already bringing as very close to the ball
    if (distanceWhenDashing < 0.7 * interceptDistance) {
      return null;
    }

    final double angleToNextBall = this.p.getAngleRel(
        this.ballPositions[interceptionTime + 1], RELATIVE.TO_BODY);
    final double distanceToNextBall = this.p
        .getDistance(this.ballPositions[interceptionTime + 1]);

    final double distanceWhenDashingNext = Math.abs(Math.sin(Math
        .toRadians(angleToNextBall))) * distanceToNextBall;

    // dashing best, as as even so it doesn't bring us very close to the ball
    // at the given intercption point, the ball of the next cycle will be very
    // close so we are unlikely to miss it
    if (distanceWhenDashingNext < 0.7 * interceptDistance) {
      return null;
    }

    this.p.copy(this.getWorld().getSelfRef());

    // try turning first otherwise
    final TurnAction turn = BasicActionFactory.turnBodyToPoint(this.p,
        this.ballPositions[interceptionTime], interceptionTime);
    this.p.predictThisAfterAction(turn);

    final double resDistance = this.getMovements().getLeftDistance(
        this.getWorld().getNumber() - 1, interceptionTime - 1,
        this.ballPositions[interceptionTime], this.p.getPosition(),
        this.p.getBodyDir(), 0, 0, interceptDistance, 1.0) + interceptDistance;

    // turning gets us nearer to the ball -> turn first
    if (resDistance < distanceWhenDashing - 0.2) {
      return turn;
    }
    else {
      return null;
    }
  }

  /**
   * gets the point to turn to when intercepting in 1 leaves time for turning.
   * (most of the times we turn in y-dir). The point depends on the players role
   * and its position
   * 
   * @return point to turn to
   */
  private Vektor getPointToTurnTo() {

    final Vektor v = Vektor.getDummyInstance();

    // KickNRun ? -> dash forward
    if (this.getWorld().getSelfRef().getPosition().y < 30) {
    	v.x = this.getWorld().getSelfRef().getPosition().x;
    	v.y = 52.5;
    	return v;
    }
    
    switch (this.getWorld().getSelfRef().getRoleByRoleNumber()) {
      case LEFT_ATTACKER:
      case LEFT_MIDFIELD:
      case LEFT_BACK_FOUR:
        v.copy(SConf.getInstance().GOAL_POST_LEFT_OTHER);

        if (this.ip.y < 40) {
          double x = this.ip.x;
          if (x < -25) {
            x = -25;
          }
          else if (x > v.x) {
            x = v.x;
          }

          v.x = x;
        }
        return v;

      case RIGHT_ATTACKER:
      case RIGHT_MIDFIELD:
      case RIGHT_BACK_FOUR:
        v.copy(SConf.getInstance().GOAL_POST_RIGHT_OTHER);

        if (this.ip.y < 40) {
          double x = this.ip.x;
          if (x > 25) {
            x = 25;
          }
          else if (x < v.x) {
            x = v.x;
          }

          v.x = x;
        }
        return v;

      default:
        return SConf.getInstance().GOAL_POS_OTHER;
    }
  }

  /**
   * the time we have after catching the ball before an opponent will reach that
   * position. <br>
   * TODO: could be more accurate as by now only the opponents ball interception
   * time is taken into account. instead his time to my ip should be used. (rw)
   * 
   * @return timediff off my and next opponent ball interception
   */
  private int getMyCatchSecurity() {

    if (this.fastestPlayers.size() == 0 || !this.fastestPlayers.get(0).isMe()) {
      return 0;
    }
    else {
      final int myTime = this.interceptionTime;
      int nextTime = -1;
      for (final Player p : this.fastestPlayers) {
        if (!p.isMe() && !p.isFriend()) {
          nextTime = this.interceptionTimes[this.getPlayerIndex(p)];
          break;
        }
      }
      if (nextTime < 0) {
        nextTime = myTime + this.maxTimeDiffToTeammate;
      }

      nextTime = Math.min(nextTime, this.ballOutsideTime);

      return nextTime - myTime;
    }
  }

  /** **************************** Prophet methods ********* */

  /**
   * @return Returns the fastestPlayer.
   */
  public LinkedList<Player> getFastestPlayer() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    return this.fastestPlayers;
  }

  /**
   * @return Returns the interceptionTime.
   */
  public int getInterceptionTime() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    return this.interceptionTime;
  }

  /**
   * @return Returns the interceptionTime.
   */
  public boolean ballOutBeforeIntercept() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    return this.ballOutsideTime <= this.getInterceptionTime();
  }

  /**
   * @return Returns the ip.
   */
  public Vektor getIp() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    return this.ip;
  }

  /**
   * @return Returns the myInterceptionTimes.
   */
  public int[] getMyInterceptionTimes() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    return this.myInterceptionTimes;
  }

  /**
   * tells who is in possesion of the ball (team of fastest to ball)
   * 
   * @return
   */
  public ControllerNumbers.TEAM inPossessionOfBall() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

//  if(!this.getWorld().getBallRef().isReliable()){
//    this.inBallPossession = TEAM.UNCERTAIN;
//    return this.inBallPossession;
//  }
    
    if (this.inBallPossession == null) {

      // don't always switch but use a tolerance as noise might lead to wrong
      // data
      final int tolerance = ((int) this.getWorld().getDistance(
          this.getWorld().getBallRef())) % 12;

      boolean our = false;
      boolean their = false;
      for (final Player p : this.fastestPlayers) {
        if (this.interceptionTimes[this.getPlayerIndex(p)] > this.interceptionTime + tolerance) {
          break;
        }

        if (p.isFriend()) {
          our = true;
        }
        else {
          their = true;
        }
      }

      if (our && their) {
        this.inBallPossession = ControllerNumbers.TEAM.UNCERTAIN;
      }
      else if (our) {
        this.inBallPossession = ControllerNumbers.TEAM.WE;
      }
      else {
        this.inBallPossession = ControllerNumbers.TEAM.OPPONENT;
      }
    }

    return this.inBallPossession;
  }

  /**
   * the ball interception time of a given player (used i.e. for gui)
   * 
   * @param number -
   *          the players number
   * @param team -
   *          one of our team?
   * @return the players interception time, {@link Integer#MAX_VALUE} if not
   *         intercepting
   */
  public int getInterceptionTime(final int number,
      final boolean team) {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }
    if (team) {
      return this.interceptionTimes[number - 1];
    }
    else {
      return this.interceptionTimes[number + 11 - 1];
    }
  }

  /**
   * This method checks, if the given player is fastest to the ball
   * 
   * @param p -
   *          the considered player
   * @return true, if he is fastest to ball when intercepting
   */
  public boolean isFastestToBall(final Player p) {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    final LinkedList<Player> player = this.getFastestPlayer();
    for (final Player fs : player) {
      if (fs.isFriend() == p.isFriend() && p.getNumber() == fs.getNumber()) {
        return true;
      }
    }
    return false;
  }

  /**
   * @return all players with the ball in their kickable margin
   */
  public LinkedList<Player> getPlayersThatCanKick() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

//    if(!this.getWorld().getBallRef().isReliable()){
//      this.playersThatCanKick.clear();
//      return this.playersThatCanKick;
//    }
    
    // no player can kick or already calculated -> return list
    if (this.interceptionTime != 0 || this.playersThatCanKick.size() > 0) {
      return this.playersThatCanKick;
    }

    for (final Player p : this.fastestPlayers) {
      if (this.getInterceptionTime(p.getNumber(), p.isFriend()) == 0) {
        this.playersThatCanKick.add(p);
      }
      // players are sorted...
      else {
        break;
      }
    }

    return this.playersThatCanKick;
  }

  /**
   * @return Returns the lastKickingPlayer.
   */
  public Player getLastKickingPlayer() {

    final Player lastKicker = this.lastKickingPlayer;

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    if (lastKicker != this.lastKickingPlayer && this.getWorld().getNumber() == 10) {
      System.out
          .println(this.getWorld().getCycleAndPlayerString() + " KICKING PLAYER " + this.lastKickingPlayer);
    }

    return this.lastKickingPlayer;
  }

  /**
   * @return Returns the ballWillGoIntoOwnGoal.
   */
  public boolean ballWillGoIntoOwnGoal() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    this.ballWillGoIntoOwnGoal = this.ip.y - 0.5 < SConf.getInstance().GOAL_POS_OWN.y && Math
        .abs(this.ip.x) < SConf.getInstance().GOAL_WIDTH / 2 + 1;

    return this.ballWillGoIntoOwnGoal;
  }

  /**
   * @return Returns the ballWillGoIntoOwnGoal.
   */
  public boolean ballWillGoIntoOpponentsGoal() {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    this.ballWillGoIntoOpponentsGoal = this.ip.y > SConf.getInstance().GOAL_POS_OTHER.y - 0.1 && Math
        .abs(this.ip.x) < SConf.getInstance().GOAL_WIDTH;

    return this.ballWillGoIntoOpponentsGoal;
  }

  /**
   * The offside is also defined by the next interception point.
   * 
   * @param team -
   *          if true we get the offside y value for our attack
   * @return the offside y value
   */
  public double getNextOffsideY(final boolean team) {

    if (!this.wasUpdated()) {
      this.calculateSituation();
    }

    if (team) {
      return this.offsideYOwn;
    }
    else {
      return this.offsideYOther;
    }
  }
}
